﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using uk.ac.soton.ses.DAL;
using System.IO;

namespace uk.ac.soton.ses
{

    public static class HDCWatcherMetadataCheck
    {


        /// <summary>
        /// Check database metadata to ensure no experiments were deleted, or files are missing. This does not find new experiments.
        /// </summary>
        public static void CheckExistingMetadata(HDCFileSystemWatcher fsw)
        {
            using (MaterialsMetadataContext context = new MaterialsMetadataContext(ExperimentMetadataContainerDB.GetEntityConnectionString()))
            {
                CheckForMissingExperiments(context, fsw);
                //The following (CheckForMissingFiles) should be handled by the scans of the experiments. it is dangerous to run on its own as it does not recognise renamed experiments
                //CheckForMissingFiles(context);

                CheckDeletedExperimentFiles(context);

                //Save all of our changes
                context.SaveChanges();
            }
        }

        /// <summary>
        /// Find experiments on disk and load the XML metadata files
        /// </summary>
        /// <param name="path">The path to start from</param>
        /// <param name="numOfLevelsToDescend">The number of levels to descend before we start recognising experiments</param>
        private static void DescendTreeAndFindExperimentsOnDisk(string path, int numOfLevelsToDescend, Dictionary<int, KeyValuePair<string, ExperimentMetadataContainerXML>> experimentsOnDisk)
        {
            foreach (string s in Directory.GetDirectories(path))
            {
                if (numOfLevelsToDescend > 0)
                {
                    DescendTreeAndFindExperimentsOnDisk(s, numOfLevelsToDescend - 1,experimentsOnDisk);
                }
                else
                {
                    if (ExperimentMetadataContainerXML.MetadataExists(s))
                    {
                        ExperimentMetadataContainerXML expMetadata = ExperimentMetadataContainer.LoadFromXML(s);
                        if (experimentsOnDisk.ContainsKey(expMetadata.Experiment.ID))
                        {
                            //If we already have a folder matching this key, we will consider the new one and existing one not correct (perhaps a copy?)
                            //Remove existing one
                            experimentsOnDisk.Remove(expMetadata.Experiment.ID);
                        }
                        else
                        {
                            //Add new one
                            experimentsOnDisk.Add(expMetadata.Experiment.ID, new KeyValuePair<string, ExperimentMetadataContainerXML>(s, expMetadata));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Iterate around all existing experiments and check to see if they have been deleted
        /// </summary>
        /// <param name="context">Pass a database context</param>
        /// <param name="fsw">The file system watcher that is running</param>
        private static void CheckForMissingExperiments(MaterialsMetadataContext context, HDCFileSystemWatcher fsw)
        {
            //Find existing experiments in our watch folder
            Dictionary<int, KeyValuePair<string, ExperimentMetadataContainerXML>> experimentsOnDisk = new Dictionary<int, KeyValuePair<string, ExperimentMetadataContainerXML>>();
            DescendTreeAndFindExperimentsOnDisk(fsw.WatchFolder, fsw.BasePathDepthInWatchFolder, experimentsOnDisk);

            //Load all database records that belong to our file system watcher
            List<Experiment> unmatchedDBExperiments = context.Experiments.Where(exp => exp.IsDeleted == false && exp.ID != 0 && exp.FileStoreOptionsExperimentID == fsw.ConfigOptions.ConfigurationContainerID).ToList();
            int numberOfExperiments = unmatchedDBExperiments.Count;

            //Find all of the ones that exist using name and ID
            for (int i = 0; i < numberOfExperiments; i++)
            {
                Experiment currentExperiment = unmatchedDBExperiments[i];

                //Get metadata in database
                ExperimentMetadataContainerDB experimentMetadataInDB = ExperimentMetadataContainer.LoadFromDB(currentExperiment.ID);

                //Get metadata from XML file in file system
                KeyValuePair<string,ExperimentMetadataContainerXML> experimentsOnDiskMetadataPair;
                ExperimentMetadataContainerXML experimentMetadataInXML = null;
                string folderPathFS = null;

                //See if we can find it elsewhere
                if (experimentsOnDisk.TryGetValue(currentExperiment.ID, out experimentsOnDiskMetadataPair))
                {
                    folderPathFS = experimentsOnDiskMetadataPair.Key;
                    experimentMetadataInXML = experimentsOnDiskMetadataPair.Value;
                }
                else if (Directory.Exists(currentExperiment.DefaultBasePath)
                    && ExperimentMetadataContainerXML.MetadataExists(currentExperiment.DefaultBasePath))
                {
                    folderPathFS = currentExperiment.DefaultBasePath;
                    experimentMetadataInXML = ExperimentMetadataContainer.LoadFromXML(currentExperiment.DefaultBasePath);
                }

                //Check it's the right one
                if(   experimentMetadataInXML !=null 
                   && experimentMetadataInDB.Experiment.ID == experimentMetadataInXML.Experiment.ID)
                {
                    ExperimentMetadataContainerFileSystem experimentMetadataInFS = ExperimentMetadataContainer.LoadFromFileSystem(new HDCPath(folderPathFS, Path.GetDirectoryName(folderPathFS), 0), experimentMetadataInDB);
                    ExperimentMetadataContainerComparer comparer = new ExperimentMetadataContainerComparer(experimentMetadataInDB, experimentMetadataInFS);

                    //Check if it was renamed
                    //if (experimentMetadataInXML.Experiment.DefaultBasePath != folderPathFS)
                    if(comparer.WasMoved || comparer.WasRenamed)
                    {
                        //We have a matching experiment with XML file on disk, but not in the right place so queue a folder check
                        HDCFileSystemWatcher.Message(string.Format("Experiment {0} {1} seems like it was renamed to {2}. Triggering folder check.", currentExperiment.ID, currentExperiment.Name, experimentMetadataInFS.Experiment.Name), System.Diagnostics.TraceLevel.Info);
                        HDCWatcherQueueProcessing.QueueFolderCheck(experimentMetadataInFS.Experiment.DefaultBasePath, fsw);
                    }

                    //Got a matching one. Remove it from the unknown list
                    unmatchedDBExperiments.RemoveAt(i);

                    //Adjust our counters to reflect that we've removed an item from the list
                    numberOfExperiments--;
                    i--;

                    continue;
                }

                //ExperimentMetadataContainerFileSystem experimentFS = new ExperimentMetadataContainerFileSystem(new HDCPath(currentExperiment.DefaultBasePath, Path.GetDirectoryName(currentExperiment.DefaultBasePath), 0), experimentInDB);

                //If we reach this point, we couldn't find a matching one
            }

            //Any experiments left in the list are missing and can marked for deletion
            foreach (Experiment unmatched in unmatchedDBExperiments)
            {
                ExperimentMetadataContainerDB experimentContainer = ExperimentMetadataContainer.LoadFromDB(unmatched.ID);
                experimentContainer.DeleteExperiment();
                experimentContainer.SaveChanges();
                HDCFileSystemWatcher.Message(string.Format("Deleted from database {0}\t{1}", unmatched.ID, unmatched.DefaultBasePath), System.Diagnostics.TraceLevel.Warning);
            }
        }

        /// <summary>
        /// Check for missing data files in existing experiments
        /// </summary>
        /// <param name="context">Pass a database context</param>
        private static void CheckForMissingFiles(MaterialsMetadataContext context)
        {
            //Now check for missing data files and mark them as deleted
            IQueryable<ExperimentDataFile> nonDeletedDataFiles = context.ExperimentDataFiles.Where(df => df.IsDeleted == false);
            foreach (ExperimentDataFile dataFile in nonDeletedDataFiles)
            {
                if (!File.Exists(dataFile.GetFullPath()))
                {
                    dataFile.IsDeleted = true;
                    HDCFileSystemWatcher.Message(string.Format("Deleting from database: {0}", dataFile.GetFullPath()),System.Diagnostics.TraceLevel.Warning);
                }
            }
        }

        /// <summary>
        /// Looks at deleted experiments and mark their data files as deleted as well
        /// </summary>
        /// <param name="context">Pass a database context</param>
        private static void CheckDeletedExperimentFiles(MaterialsMetadataContext context)
        {
            //Now check for experiment database records marked as deleted, and mark their data files as deleted
            IQueryable<ExperimentDataFile> deletedFiles = context.Experiments.Where(exp => exp.IsDeleted == true && exp.ID != 0).SelectMany(exp => exp.ExperimentDataFiles.Where(df => df.IsDeleted == false));
            foreach (ExperimentDataFile deletedFile in deletedFiles)
            {
                deletedFile.IsDeleted = true;
                HDCFileSystemWatcher.Message(string.Format("Deleting from database: {0}, because experiment {1} has been deleted", deletedFile.GetFullPath(), deletedFile.ExperimentID),System.Diagnostics.TraceLevel.Warning);
            }
        }
    }
}
